package networkSimulation.simulation.virus.simulation

import networkSimulation.visualisation.graph.graphviz.GraphvizVisualiser
import networkSimulation.simulator.Simulator
import networkSimulation.framework.{Graph, Edge}
import util.Random

import networkSimulation.statistic.basicStatistics.{NodeXY, EdgeXY, EdgeNodeRatioPie, EdgeNodeRatioBar}
import networkSimulation.statistic.StatisticsManager
import networkSimulation.simulation.virus.InfectablePerson
import networkSimulation.simulation.virus.statistic._

/**
 * This simulation represents the spread of a virus.
 *
 * @simulation-type: Turn-based
 * @visualisation-type: Off-line
 * @visualisation-framework: GraphViz
 * @author  Tim Caljé
 *          Jeroen De Raedt
 *          Jorick Van Aken
 */
class Virus(graph: Graph[InfectablePerson,Edge[InfectablePerson]]) extends Simulator[InfectablePerson,Edge[InfectablePerson]](graph) with GraphvizVisualiser[InfectablePerson,Edge[InfectablePerson]]{

  /**
   * Variable holding the path for the visual representations
   */
  private var outputPath = ""

  /**
   * Variable representing how many days it takes before an infected person dies
   */
  private val nbDaysBeforeDying = 4

  /**
   * The statistics manager for this simulation
   */
  private val statisticsManager = new StatisticsManager[InfectablePerson,Edge[InfectablePerson]]()

  /**
   * Add the specific statistics to the graph.
   */
  statisticsManager.addStatistic(new AmountsInfectedLine[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new EdgeNodeRatioBar[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new EdgeNodeRatioPie[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new EdgeXY[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new NodeXY[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new AmountInfectedBar[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new AmountInfectedPie[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new AmountsInfectedXY[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new AmountsVirusLine[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new EndAmountsVirusBar[InfectablePerson,Edge[InfectablePerson]]())
  statisticsManager.addStatistic(new BeginAmountsVirusBar[InfectablePerson,Edge[InfectablePerson]]())

  /**
   * Start this simulation.
   *
   * @param: turns
   *         The number of turns this simulation will run.
   * @param: path
   *         The path to the folder that will hold all (temporary) files.
   */
  def startSimulation( turns : Int, path : String){
    this.outputPath = path
    clean(this.outputPath)
    super.saveVisualGraph(path)                             // take snapshot of initial graph
    val rand = new Random()
    val index = rand.nextInt(this.graph.nodes.length-1)
    this.graph.nodes.apply(index).infected = true           // infect a random node
    super.saveVisualGraph(path)                             // take snapshot of graph with infection source
    for (i <- 0 until turns)
      run()
    statisticsManager.generateStatistics(outputPath);
  }

  /**
   * Extended method to run a simulation.
   * This extension:
   *    - publishes the simulation event to listeners
   *    - creates a graphical presentation
   *    - updates the statistics
   */
  @Override
  override def run(){
    super.run()

    this.publish()
    super.saveVisualGraph(this.outputPath)
    statisticsManager.updateStatistics(outputPath,graph)
  }

  /**
   * Extended functionality to load a specific InfectedPerson Node from XML
   */
  @Override
  override protected def loadNode(node: xml.NodeSeq) = {
    var name = ""
    var infected : Boolean = false
    var isDead : Boolean = false
    var nbDaysOfInfection : Int = 0
    var immune : Boolean = false
    for(field <- (node \\ "field")) {
      (field \\ "@name").text match {
        case "name" => name = field.text
        case "infected" => if(field.text == "true") { infected = true } else { infected = false }
        case "isDead" => if(field.text == "true") { isDead = true } else { isDead = false }
        case "nbDaysOfInfection" => nbDaysOfInfection = field.text.toInt
        case "immune" => if(field.text == "true") { immune = true } else { immune = false }
        case _ => throw new Exception("Unknow field: " + field \\ "@name")
      }
    }
    new InfectablePerson(name,infected,immune,isDead, nbDaysOfInfection)
  }

}